/*
 * @Author: 黄威
 * @Date: 2022-01-14 16:15:23
 * @LastEditors: wanghan
 * @LastEditTime: 2022-03-18 17:04:32
 * @Description: 绘图插件
 */
import type { BindMap, PlottingOption, AnimationOption } from 'types'

import l from 'leaflet'
import * as Cesium from 'Cesium'
import StylePanel2D from './plotting/StylePanel2D'
import StylePanel3D from './plotting/StylePanel3D'

// supermap L
const L = l as any
// supermap cesium
const cesium = Cesium as any

// 设置标号类别
declare type PlottingMode = 'draw' | 'edit' | 'select'

// 生成唯一id
let lastId = 0
function stamp() {
    return 'plotting-layer-' + ++lastId
}

// 代理派发事件
function proxyEvent(vm: any, eventName: string, pickName?: string) {
    return function (evt: any, ...rest: any[]) {
        const data = pickName ? evt[pickName] : evt
        vm.$emit(eventName, data, ...rest)
    }
}

// 获取tip
function getTip(options: any, tip2?: any): string {
    const { lib, code, tip } = options

    if (tip) return tip
    if (!tip2) return '左键点击添加点位，双击完成，右键结束！'
    if (typeof tip2 === 'function') return tip2(lib, code)
    else return tip2
}

// 标绘基类
export abstract class PlottingBase {
    static extends = [
        'clearPlotting',
        'setPlottingMode',
        'plottingObj2Json',
        'json2PlottingObj',
        'getPlottingObj',
        'deletePlottingObj',
        'selectPlottingObj',
        'unselectPlottingObj',
        'getSelectedPlottingObj',
        'getAllPlottingObj',
        'getPlottingObjStyle',
        'updatePlottingObjStyle',
        'execute',
        'createAnimation',
        'removeAnimation',
        'removeAllAnimation',
        'play',
        'pause',
        'stop',
        'reset',
    ]

    vm: BindMap
    options: PlottingOption

    // 所有的标绘图层
    layers: { [id: string]: any }
    // 当前激活的图层
    layer: any

    // 模式
    mode: PlottingMode

    // 动态标绘模块
    plotting: any
    // 控制器
    drawControl: any
    editControl: any
    // 动画管理者
    goAnimationManager: any

    constructor(vm: BindMap, options: PlottingOption) {
        this.vm = vm
        this.options = options

        // 标绘图层
        this.layers = {}
        this.createLayer('default')
        this.layer = this.layers.default

        // 初始化控件
        this._init()
        // 初始化模式
        this.mode = 'select'
        this.setPlottingMode('select')
    }

    // 一些基类
    abstract _init(): void
    abstract _createLayer(id: string): void
    abstract _removeLayer(layer: any): void
    abstract _switchLayer(layer: any): void

    abstract _plottingObj2Json(f: any): string
    abstract _json2PlottingObj(json: string, callback: any): any
    abstract _deletePlottingObj(fs: any[]): void
    abstract _selectPlottingObj(fs: any[]): void
    abstract unselectPlottingObj(ids?: string | string[]): void
    abstract getSelectedPlottingObj(): any[]
    abstract getAllPlottingObj(layerid?: string): any[]
    /* --- 样式获取和设置 --- */
    abstract getPlottingObjStyle(ids: string | string[]): any
    abstract updatePlottingObjStyle(ids: string | string[], updated: any): any

    /* --- 图层操作 --- */

    // 清空标绘图层
    clearPlotting(id?: string) {
        const clearLayer = function (layer: any) {
            if (!layer) return

            layer.removeAllFeatures
                ? layer.removeAllFeatures()
                : layer.removeAll()
        }

        if (id) {
            clearLayer(this.layers[id])
        } else {
            Object.values(this.layers).forEach(clearLayer)
        }
    }
    // 创建图层
    createLayer(id?: string) {
        id = id || stamp()

        const plottingLayer = this._createLayer(id)
        this.layers[id] = plottingLayer

        return id
    }
    // 销毁图层
    removeLayer(id: string) {
        // 默认图层不可移除
        if (id === 'default') return false

        // 移除图层
        const layer = this.layers[id]
        if (!layer) return false

        // 存在图层
        delete this.layers[id]
        this._removeLayer(layer)

        // 如果是当前激活的图层（切换到默认图层）
        if (this.layer === layer) {
            this.switchLayer('default')
        }

        return true
    }
    // 切换图层（即当前绘制的图层）
    switchLayer(id: string) {
        if (id in this.layers) {
            this.layer = this.layers[id]
            this._switchLayer(this.layer)
        } else {
            this.createLayer(id)
        }
    }

    // todo 设置图层状态（选中-只读，绘制-不可编辑和选中，编辑-争对存在标号）
    // setLayerState() {}

    /* --- 控制器操作 --- */

    // 设置模式（绘制，编辑，选中）
    abstract setPlottingMode(mode: PlottingMode, options?: any): void

    /* --- 标号操作 --- */
    // 标号转json
    plottingObj2Json(feature: any) {
        const serialize = this._plottingObj2Json

        if (Array.isArray(feature)) return feature.map(serialize)
        else return serialize(feature)
    }
    // json转标号
    json2PlottingObj(json: any, callback?: (f: any) => void) {
        callback = callback || (res => res)

        const self = this
        const deserialize = async function (j: any) {
            // 统一为字符串
            if (typeof j !== 'string') j = JSON.stringify(j)

            return new Promise((resolve, reject) => {
                const cb = (ret: any) => resolve(ret.feature)

                try {
                    self._json2PlottingObj(j, cb)
                } catch (error) {
                    reject(error)
                }
            })
        }
        // 是个数组
        if (Array.isArray(json))
            return Promise.all(json.map(deserialize)).then(callback)
        else return deserialize(json).then(callback)
    }
    // 获取指定的标号
    getPlottingObj(ids: string | string[]) {
        if (Array.isArray(ids))
            return ids.map(id => this.layer.getFeatureByUuid(id))
        else return this.layer.getFeatureByUuid(ids)
    }
    getPlottingObj2(ids: string | string[]): any[] {
        let fs = this.getPlottingObj(ids)
        if (!Array.isArray(fs)) fs = [fs]
        fs = fs.filter((f: any) => !!f)
        return fs
    }
    // 删除标号
    deletePlottingObj(ids: string | string[]) {
        const fs = this.getPlottingObj2(ids)

        this._deletePlottingObj(fs)
    }
    // 选中标号
    selectPlottingObj(ids: string | string[]) {
        const fs = this.getPlottingObj2(ids)
        this._selectPlottingObj(fs)
    }

    // 更新标号（属性）
    // abstract updatePlottingObj(): void

    /* --- 动画操作 TODO --- */
    // 执行
    execute() {
        this.goAnimationManager.execute()
    }
    // 添加动画
    abstract _createAnimation(opt: AnimationOption): void
    // 删除动画
    abstract _removeAnimation(fs: any[]): void
    createAnimation(opts: AnimationOption | AnimationOption[]) {
        if (!Array.isArray(opts)) opts = [opts]
        opts.forEach(opt => {
            this._createAnimation(opt)
        })
    }
    // 删除动画
    removeAnimation(ids: string | string[]) {
        const fs = this.getPlottingObj2(ids)

        this._removeAnimation(fs)
    }
    // 删除所有
    removeAllAnimation() {
        this.goAnimationManager.reset()
        this.goAnimationManager.removeAllGOAnimation()
    }

    /* --- 态势 TODO --- */
    // 播放
    abstract play(): void
    // 暂停
    abstract pause(): void
    // 停止
    abstract stop(): void
    // 复位
    abstract reset(): void
}

// 二维标绘
class Plotting2D extends PlottingBase {
    stylePanel!: StylePanel2D

    _init(): void {
        const drawControl = L.supermap.plotting.drawControl(this.layer)
        drawControl.addTo(this.vm.map)
        const editControl = L.supermap.plotting.editControl()
        editControl.addTo(this.vm.map)

        this.drawControl = drawControl
        this.editControl = editControl

        const self = this

        // 右键结束事件
        this.vm.map.on('contextmenu', function () {
            if (self.mode === 'draw') self.setPlottingMode('edit')
        })
        // 标绘完成事件
        drawControl.on(
            SuperMap.Plot.Event.featureadded,
            proxyEvent(this.vm, 'featureadded', 'feature')
        )
        // 标号编辑完成事件
        editControl.on(
            SuperMap.Plot.Event.featuresmodified,
            proxyEvent(this.vm, 'featuresmodified', 'features')
        )
        // 标号选中事件
        editControl.on(
            SuperMap.Plot.Event.featuresselected,
            function (evt: any) {
                // 选中状态
                if (self.mode === 'select') {
                    const f = evt.features[0]
                    self.unselectPlottingObj()
                    proxyEvent(self.vm, 'featureselected')(f)
                }
                // 编辑状态
                else proxyEvent(self.vm, 'featuresselected')(evt.features)
            }
        )
        // 标号取消选中事件
        editControl.on(
            SuperMap.Plot.Event.featuresunselected,
            function (evt: any) {
                if (self.mode === 'edit') {
                    proxyEvent(self.vm, 'featuresunselected')(evt.features)
                }
            }
        )
        this.plotting = L.supermap.plotting.getControl(
            this.vm.map,
            this.options.url
        )
        this.stylePanel = new StylePanel2D(this.plotting)
        this.goAnimationManager = this.plotting.getGOAnimationManager()
    }
    _createLayer(id: string): string {
        const plottingLayer = L.supermap.plotting.plottingLayer(
            id,
            this.options.url
        )
        plottingLayer.addTo(this.vm.map)

        return plottingLayer
    }
    _removeLayer(layer: any): void {
        layer.remove()
    }
    _switchLayer(layer: any): void {
        this.drawControl.setDrawingLayer(layer)
    }

    _plottingObj2Json(f: any): string {
        return f.toGeoJsonStr()
    }
    _json2PlottingObj(json: string, callback: any) {
        return this.layer.geoJsonToFeature(json, callback)
    }
    _deletePlottingObj(fs: any[]): void {
        this.layer.removeFeatures(fs)
    }
    _selectPlottingObj(fs: any[]): void {
        this.editControl.selectFeatures(fs)
    }
    // 取消选中标号
    unselectPlottingObj(ids?: string | string[]) {
        let fs = []
        // 如果入参为空，取消全部选中
        if (!ids) {
            fs = this.getSelectedPlottingObj()
        } else {
            fs = this.getPlottingObj2(ids)
        }

        this.editControl.unselectFeatures(fs)
    }
    // 获取选中的标号
    getSelectedPlottingObj(): any[] {
        return this.editControl.getSelectedFeatures()
    }
    getAllPlottingObj(layerid?: string): any[] {
        layerid = layerid || 'default'
        const layer = this.layers[layerid] || this.layer
        return layer.features
    }

    // 设置标绘模式
    setPlottingMode(mode: 'draw' | 'edit' | 'select', options?: any): void {
        const drawControl = this.drawControl
        const editControl = this.editControl

        drawControl.handler.disable()
        editControl.remove()

        // 当前模式
        this.mode = mode

        // 结束原生绘制
        this.vm.endDraw()
        this.vm.hideTip()

        if (mode === 'draw') {
            const { lib, code } = options
            drawControl.handler.libID = lib
            drawControl.handler.code = code
            drawControl.handler.serverUrl = this.options.url
            drawControl.handler.enable()

            // 展示 tip
            this.vm.showTip(getTip(options, this.options.tip))
        } else {
            editControl.addTo(this.vm.map)
        }
    }

    // 获取样式
    getPlottingObjStyle(ids: string | string[]) {
        const fs = this.getPlottingObj2(ids)
        return this.stylePanel.collectionPropertyGridRows(fs)
    }
    updatePlottingObjStyle(ids: string | string[], updated: any) {
        if (!Array.isArray(updated)) updated = [updated]

        const fs = this.getPlottingObj2(ids)
        updated.forEach((up: any) =>
            this.stylePanel.updateSelectFeature(up, fs)
        )
    }
    // 动画相关
    _createAnimation(opt: AnimationOption) {
        const f = this.getPlottingObj(opt.id)
        const type = SuperMap.Plot.GOAnimationType['ANIMATION_' + opt.type]
        const name = opt.name + '-' + SuperMap.Plot.PlottingUtil.generateUuid()
        let goAnimation = this.goAnimationManager.createGOAnimation(
            type,
            name,
            f
        )
        goAnimation = Object.assign(goAnimation, opt.options)
    }
    _removeAnimation(fs: any[]): void {
        fs.forEach((f: any) => {
            const animations =
                this.goAnimationManager.findGOAnimationsByFeature(f)
            for (let i = 0; i < animations.length; i++) {
                this.goAnimationManager.removeGOAnimation(animations[i])
            }
        })
    }

    // 播放
    play() {
        const goAnimations = this.goAnimationManager.goAnimations
        if (null === goAnimations) return

        goAnimations.forEach((animat: any) => animat.play())
    }
    // 暂停
    pause() {
        const goAnimations = this.goAnimationManager.goAnimations
        if (null === goAnimations) return

        goAnimations.forEach((animat: any) => animat.pause())
    }
    // 停止
    stop() {
        const goAnimations = this.goAnimationManager.goAnimations
        if (null === goAnimations) return

        goAnimations.forEach((animat: any) => animat.stop())
    }
    // 复位
    reset() {
        const goAnimations = this.goAnimationManager.goAnimations
        if (null === goAnimations) return

        goAnimations.forEach((animat: any) => animat.reset())
    }
}

// 三维标绘
class Plotting3D extends PlottingBase {
    private stylePanel!: StylePanel3D

    _init(): void {
        const scene = this.vm.map.scene

        //绘制控件
        const drawControl = new cesium.PlotDrawControl(scene, this.layer)
        //编辑控件
        const editControl = new cesium.PlotEditControl(scene, this.layer)

        // 控件
        this.drawControl = drawControl
        this.editControl = editControl

        const self = this

        // 绘制完成事件
        drawControl.drawFinishEvent.addEventListener(
            proxyEvent(this.vm, 'featureadded')
        )
        // 绘制状态被关闭事件
        drawControl.drawControlEndEvent.addEventListener(function () {
            if (self.mode === 'draw') self.setPlottingMode('edit')
        })
        // 标号被编辑事件
        editControl.ModifiedEvent.addEventListener(function (f: any) {
            proxyEvent(self.vm, 'featuresmodified')([f])
        })
        // 标号被选中事件
        editControl.SelectedEvent.addEventListener(function (f: any) {
            // 选中状态
            if (self.mode === 'select') {
                setTimeout(() => self.unselectPlottingObj(), 100)
                proxyEvent(self.vm, 'featureselected')(f)
            }
            // 编辑状态
            else proxyEvent(self.vm, 'featuresselected')([f])
        })
        // 标号被释放事件
        editControl.UnSelectedEvent.addEventListener(function () {
            if (self.mode === 'edit') {
                proxyEvent(self.vm, 'featuresunselected')([])
            }
        })

        // 初始化标号库（必须）
        this.plotting = cesium.Plotting.getInstance(this.options.url, scene)

        this.stylePanel = new StylePanel3D(this.plotting)
        this.goAnimationManager = this.plotting.getGOAnimationManager()
    }
    _createLayer(id: string): void {
        const scene = this.vm.map.scene

        const plottingLayer = new cesium.PlottingLayer(scene, id)
        plottingLayer._isVue = true
        scene.plotLayers.add(plottingLayer)

        return plottingLayer
    }
    _removeLayer(layer: any): void {
        const scene = this.vm.map.scene
        scene.plotLayers.remove(layer)
    }
    _switchLayer(layer: any): void {
        this.drawControl.setPlottingLayer(layer)
    }

    _plottingObj2Json(f: any): string {
        return cesium.GeoGraphicObject.toGeoJson(f)
    }
    _json2PlottingObj(json: string, callback: any) {
        return this.layer.geoJsonToGeoGraphicObject(JSON.parse(json), callback)
    }
    _deletePlottingObj(fs: any[]): void {
        fs.forEach((f: any) => this.layer.removeGeoGraphicObject(f))
    }
    _selectPlottingObj(fs: any[]): void {
        if (fs[0]) {
            this.editControl.DoSelectObjectEdit(this.editControl, fs[0])
        }
    }
    // 取消选中标号
    unselectPlottingObj(ids?: string | string[]) {
        if (this.getSelectedPlottingObj().length) {
            this.editControl.releaseSelection()
            this.editControl.UnSelectedEvent.raiseEvent()
        }
    }
    // 获取当前选中的标号
    getSelectedPlottingObj(): any[] {
        const g = this.editControl._graphicObject
        if (g) return [g]
        else return []
    }
    getAllPlottingObj(layerid?: string): any[] {
        layerid = layerid || 'default'
        const layer = this.layers[layerid] || this.layer
        return layer.geoGraphicObjects
    }

    setPlottingMode(mode: PlottingMode, options?: any): void {
        const drawControl = this.drawControl
        const editControl = this.editControl

        drawControl.deactivate()
        editControl.deactivate()

        this.mode = mode

        // 结束原生绘制
        this.vm.endDraw()
        this.vm.hideTip()

        if (mode === 'draw') {
            const { lib, code } = options
            drawControl.setAction(lib, code)
            drawControl.serverUrl = this.options.url
            drawControl.activate()

            // 展示 tip
            this.vm.showTip(getTip(options, this.options.tip))
        } else {
            editControl.activate()
        }
    }

    getPlottingObjStyle(ids: string | string[]) {
        const fs = this.getPlottingObj2(ids)
        if (fs.length) {
            return this.stylePanel.collectionPropertyGridRows(fs[0])
        } else {
            return []
        }
    }
    updatePlottingObjStyle(ids: string | string[], updated: any) {
        if (!Array.isArray(updated)) updated = [updated]

        const fs = this.getPlottingObj2(ids)
        updated.forEach((up: any) =>
            fs.forEach(f => this.stylePanel.updateSelectFeature(up, f))
        )
    }
    // 动画相关
    _createAnimation(opt: AnimationOption) {
        const f = this.getPlottingObj(opt.id)
        const type = SuperMap.Plot.GOAnimationType['ANIMATION_' + opt.type]
        const name = opt.name + opt.id
        const goAnimation = this.goAnimationManager.createGOAnimation(
            type,
            name,
            f
        )
        Object.assign(goAnimation, opt.options)
    }
    _removeAnimation(fs: any[]): void {
        fs.forEach((f: any) => {
            this.goAnimationManager.removeGOAnimationByFeature(f)
        })
    }

    // 播放
    play() {
        this.goAnimationManager.play()
    }
    // 暂停
    pause() {
        this.goAnimationManager.pause()
    }
    // 停止
    stop() {
        this.goAnimationManager.stop()
    }
    // 复位
    reset() {
        this.goAnimationManager.reset()
    }
}

export default class PlottingPlugin {
    constructor(vm: BindMap, options: PlottingOption) {
        const clazz = vm.MapType === '2D' ? Plotting2D : Plotting3D
        return new clazz(vm, options)
    }
}
